package com.cui.project.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.api.common.alert.CommonAlertHelper;
import com.api.common.model.entity.User;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.api.common.common.ErrorCode;
import com.cui.project.constant.UserConstant;
import com.cui.project.mapper.UserMapper;
import com.cui.project.exception.BusinessException;
import com.cui.project.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import java.util.Objects;
import java.util.concurrent.TimeUnit;


/**
 * 用户服务实现类
 *
 * @author cui
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Resource
    private UserMapper userMapper;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    @Resource
    private CommonAlertHelper alertHelper;

    /**
     * 盐值，混淆密码
     */
    private static final String SALT="cui123456";

    private static final String REGISTER_LOCK_PREFIX="api:user:register:lock:";

    private static final String LOGIN_FAIL_PREFIX="api:user:login:fail";
    private static final int  MAX_LOGIN_FAIL=5;
    private static final  long LOCK_TIME_SECONDS=1800; // 30分钟

    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
        }
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
        }
        // 密码和校验密码相同
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
        }

        //使用分布式锁防止并发注册
        String lockKey=REGISTER_LOCK_PREFIX+userAccount;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 尝试获取锁，最多等待3秒，锁10秒后自动释放
            boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if(!locked){
                throw new BusinessException(ErrorCode.SYSTEM_ERROR,"系统繁忙，请稍后重试");
            }
            // 账户不能重复
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("userAccount", userAccount);
            long count = userMapper.selectCount(queryWrapper);
            if (count > 0) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
            }
            // 2. 加密
            String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
            //分配 accessKey secretKey
            String accessKey = DigestUtil.md5Hex(SALT + userAccount + RandomUtil.randomNumbers(5));
            String secretKey = DigestUtil.md5Hex(SALT + userAccount+userPassword + RandomUtil.randomNumbers(8));

            // 3. 插入数据
            User user = new User();
            user.setUserAccount(userAccount);
            user.setUserPassword(encryptPassword);
            user.setAccessKey(accessKey);
            user.setSecretKey(secretKey);
            boolean saveResult = this.save(user);
            if (!saveResult) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败，数据库错误");
            }
            return user.getId();
        }catch (DuplicateKeyException e){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账号已存在");
        }
        catch (InterruptedException e){
            Thread.currentThread().interrupt();
            throw new BusinessException(ErrorCode.SYSTEM_ERROR,"注册失败");
        }finally {
            if(lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
        //userAccount.intern() String.intern() 会将字符串放入字符串常量池（JDK7+在堆中），永不被GC
//        synchronized (userAccount.intern()) {
//
//        }
    }

    @Override
    public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误");
        }
        if (userPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
        }

        //检测账号是否被锁定
        String lockKey=LOGIN_FAIL_PREFIX+userAccount;
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        Integer failCount = (Integer)operations.get(lockKey);
        if(failCount!=null&&failCount>=MAX_LOGIN_FAIL){
            Long expire = redisTemplate.getExpire(lockKey, TimeUnit.SECONDS);
            throw new BusinessException(ErrorCode.OPERATION_ERROR,
                    "账号已被锁定，请" + (expire / 60) + "分钟后再试");
        }
        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 查询用户是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        User user = userMapper.selectOne(queryWrapper);

        // 用户不存在或判断密码是否正确
        if (user == null||!encryptPassword.equals(user.getUserPassword())) {
            //登录失败，增加失败次数
            if(failCount==null){
                operations.set(lockKey,1,LOCK_TIME_SECONDS,TimeUnit.SECONDS);
            }else{
                operations.increment(lockKey);
                if(failCount+1>=MAX_LOGIN_FAIL){
                    log.warn("账号登录失败次数过多，已锁定: {}", userAccount);
                    //当达到锁定阈值时，发送告警到用户邮箱
                    String email = Objects.requireNonNull(user).getEmail();
                    if(StringUtils.isNotBlank(email)){
                        alertHelper.userLoginFailure(
                                user.getId(),
                                userAccount,
                                "连续登陆失败",
                                failCount+1,
                                email // 发送到用户邮箱
                        );
                    }
                }
            }
            log.info("用户登录失败: {}, 失败次数: {}", userAccount, failCount == null ? 1 : failCount + 1);
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
        }
        // 3. 记录用户的登录态
        HttpSession session = request.getSession();
        session.setAttribute(UserConstant.USER_LOGIN_STATE, user);
//        log.info("session Id: {}",session.getId());

        //登录成功，清除失败记录
        redisTemplate.delete(lockKey);
        return user;
    }

    @Override
    public String refreshAccessKey(User user) {
        Long id = user.getId();
        String userAccount = user.getUserAccount();
        String userPassword = user.getUserPassword();
        if (id <= 0 || StringUtils.isBlank(userAccount)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //重新获取一个签名
        String accessKey = DigestUtil.md5Hex(SALT + userAccount + RandomUtil.randomNumbers(5));
        String secretKey = DigestUtil.md5Hex(SALT + userAccount + userPassword + RandomUtil.randomNumbers(5));
        User newUser = new User();
        newUser.setId(id);
        newUser.setAccessKey(accessKey);
        newUser.setSecretKey(secretKey);
        newUser.setUserPassword(userPassword);
        if (!this.updateById(newUser)) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "签名更新失败");
        }
        return accessKey;
    }

    /**
     * 获取当前登录用户
     *
     * @param request
     * @return
     */
    @Override
    public User getLoginUser(HttpServletRequest request) {
        // 先判断是否已登录
        HttpSession session = request.getSession();

        Object userObj = session.getAttribute(UserConstant.USER_LOGIN_STATE);
        User currentUser = (User) userObj;
        if (currentUser == null || currentUser.getId() == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        // 从数据库查询（追求性能的话可以注释，直接走缓存）
        long userId = currentUser.getId();
        currentUser = this.getById(userId);
        if (currentUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        return currentUser;
    }

    /**
     * 是否为管理员
     *
     * @param request
     * @return
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        // 仅管理员可查询
        Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
        User user = (User) userObj;
        return user != null && UserConstant.ADMIN_ROLE.equals(user.getUserRole());
    }

    /**
     * 用户注销
     *
     * @param request
     */
    @Override
    public boolean userLogout(HttpServletRequest request) {
        if (request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE) == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
        }
        // 移除登录态
        request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE);
        return true;
    }

}




